﻿using System;
using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace JsonRpcRT.BasicCommunication
{
    public delegate EventHandler ConnectedEventHandler();

    public delegate EventHandler DisconnectedEventHandler();

    public delegate void RecievedDataEventHandler(string data);

    public delegate EventHandler SentDataEventHandler(string data);

    public delegate void LogEventHandler(string text);

    public class BasicSocketOperationsController
    {
        private readonly uint _bufferSize;
        private readonly AsyncLock _connectLock = new AsyncLock();
        private readonly DataReader _reader;
        private readonly AsyncLock _sendLock = new AsyncLock();
        private readonly StreamSocket _socket;
        private readonly DataWriter _writer;

        public BasicSocketOperationsController(string hostName, string hostPort, uint bufferSize)
        {
            _socket = new StreamSocket();
            HostName = new HostName(hostName);
            HostPort = hostPort;
            _bufferSize = bufferSize;

            _reader = new DataReader(_socket.InputStream) {InputStreamOptions = InputStreamOptions.Partial};
            _writer = new DataWriter(_socket.OutputStream);
        }

        public HostName HostName { get; set; }
        public string HostPort { get; set; }
        public bool IsConnected { get; set; }

        public event ConnectedEventHandler Connected;
        public event DisconnectedEventHandler Disconnected;
        public event RecievedDataEventHandler DataRecieved;
        public event SentDataEventHandler DataSent;
        public event LogEventHandler Log;

        private void OnConnected()
        {
            ConnectedEventHandler handler = Connected;
            if (handler != null) handler();
        }

        private void OnDisconnected()
        {
            DisconnectedEventHandler handler = Disconnected;
            if (handler != null) handler();
        }

        private void OnDataRecieved(string data)
        {
            RecievedDataEventHandler handler = DataRecieved;
            if (handler != null) handler(data);
        }

        private void OnDataSent(string data)
        {
            SentDataEventHandler handler = DataSent;
            if (handler != null) handler(data);
        }

        private void OnLog(string text)
        {
            LogEventHandler handler = Log;
            if (handler != null) handler(text);
        }

        public async Task<bool> Init()
        {
            bool initSuccessFull;

            initSuccessFull = await Connect();

            if (initSuccessFull)
            {
                RecieveLoop();
            }

            return initSuccessFull;
        }

        private async Task<bool> Connect()
        {
            bool connectionSuccessfull = false;

            using (await _connectLock.LockAsync())
            {
                if (IsConnected)
                {
                    return true;
                }

                try
                {
                    await _socket.ConnectAsync(HostName, HostPort);

                    connectionSuccessfull = true;

                    OnLog("Connected!");
                    OnConnected();
                    IsConnected = true;
                }
                catch (Exception exception)
                {
                    OnLog("Connect failed with error: " + exception.Message);
                }

                return connectionSuccessfull;
            }
        }

        private void Disconnect()
        {
            OnLog("Disconnected!");
            OnDisconnected();
            IsConnected = false;
        }

        private async void RecieveLoop()
        {
            bool running = true;

            while (running)
            {
                try
                {
                    uint numStrBytes = await _reader.LoadAsync(_bufferSize);

                    if (numStrBytes == 0)
                    {
                        Disconnect();
                        return;
                    }

                    string msg = _reader.ReadString(numStrBytes);

                    OnLog(string.Format("Recieved: {0}", msg));
                    OnDataRecieved(msg);
                }
                catch (Exception exception)
                {
                    OnLog("Recieve failed with error: " + exception.Message);

                    Disconnect();
                    running = false;
                }
            }
        }

        public async Task<bool> Send(string text)
        {
            using (await _sendLock.LockAsync())
            {
                bool sendSuccessfull = false;

                if (IsConnected || await Init())
                {
                    try
                    {
                        string sendData = text;
                        _writer.WriteString(sendData);
                        await _writer.StoreAsync();

                        OnLog("Data was sent " + text);
                        OnDataSent(text);

                        sendSuccessfull = true;
                    }
                    catch (Exception exception)
                    {
                        OnLog("Send failed with error: " + exception.Message);

                        Disconnect();
                        sendSuccessfull = false;
                    }
                }

                return sendSuccessfull;
            }
        }
    }
}